﻿// -----------------------------------------------------------------------
// <copyright file="ComplementaryPairEncryptor.cs" company="Microsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace DNADataHiding.Model.Encryption
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using DNADataHiding.Exceptions;
    using DNADataHiding.Model.Shared;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class ComplementaryPairEncryptor : IEncryptor
    {
        private int _longestComplementsLength;

        public ComplementaryPairEncryptor(int longestComplementsLength)
        {
            this._longestComplementsLength = longestComplementsLength;
        }

        public ComplementaryPairEncryptor()
        {
        }

        public int LongestComplementsLength
        {
            get
            {
                return _longestComplementsLength;
            }
        }

        private string GetLongestComplementarySubstring(DNASequence sequence)
        {
            int retPos = -1;
            int retLength = -1;

            int basePos = 0;

            while (basePos < sequence.Length())
            {
                int baseLength = 1;
                bool keepSearching = true;    

                while (keepSearching)
                {
                    string baseSeq = sequence.ToString().Substring(basePos, baseLength);
                    string complement = ComplementRule.GetComplement(baseSeq);

                    //si encuentra el complemento en el resto
                    if (sequence.ToString().IndexOf(complement, basePos + baseLength) > -1)
                    {
                        if (baseLength > retLength)
                        {
                            retPos = basePos;
                            retLength = baseLength;
                        }

                        baseLength++;
                    }
                    else
                    {
                        keepSearching = false;
                    }
                }

                basePos++;    
            }

            return sequence.ToString().Substring(retPos, retLength);
        }

        private IList<string> GetLongestComplementarySubstringList(DNASequence sequence)
        {
            IList<string> retList = new List<string>();

            int basePos = 0;

            //mientras no se me termine la secuencia
            while (basePos < sequence.Length())
            {
                int baseLength = 1;
                bool keepSearching = true;

                //mientras tenga que seguir buscando secuencias mas largas
                while (keepSearching)
                {
                    //tomo el par complementario
                    string baseSeq = sequence.ToString().Substring(basePos, baseLength);
                    string complement = ComplementRule.GetComplement(baseSeq);

                    //si encuentra el complemento en el resto
                    if (sequence.ToString().IndexOf(complement, basePos + baseLength) > -1)
                    {
                        baseLength++;
                    }
                    else
                    {
                        //no encontro el complemento en el resto
                        keepSearching = false;

                        if (retList.Count() > 0)
                        {
                            int maxLength = retList[0].Length;

                            if (maxLength < (baseLength - 1))
                            {
                                retList.Clear();
                            }

                            if (maxLength <= (baseLength - 1))
                            {
                                retList.Add(sequence.ToString().Substring(basePos, baseLength - 1));
                            }
                        }
                        else
                        {
                            //me guardo el par (la secuencia) anterior
                            retList.Add(sequence.ToString().Substring(basePos, baseLength - 1));
                        }
                    }
                }

                //incremento el punto de partida para las secuencias
                basePos++;
            }

            return retList;
        }

        private bool CompareSequenceList(IList<string> left, IList<string> right)
        {
            if (left.Count != right.Count)
            {
                return false;
            }

            for (int i = 0; i < left.Count; i++)
            {
                if (left[i].Substring(1, left[i].Count() - 2)  != right[i])
                //if (left[i].Trim(new char[] { 'T' }) != right[i])
                {
                    return false;
                }
            }

            return true;
        }

        public DNASequence Encrypt(string message, DNASequence baseSequence)
        {
            int attempts = 0;
            bool areEqual = false;
            StringBuilder sb = new StringBuilder();

            string longestPair = this.GetLongestComplementarySubstringList(baseSequence)[0];
            int k = longestPair.Length;

            BinaryCodingRule binaryCodingRule = new BinaryCodingRule();
            string binaryMessage = EncryptionUtils.GetBinaryMessage(message);

            int p = binaryMessage.Length / 2;

            IList<char> messageSegments = new List<char>();

            for (int i = 0; i < p; i++)
            {
                string nucleotideBin = binaryMessage.Substring(i * 2, 2);
                char nucleotide = binaryCodingRule.FromBinary(nucleotideBin);
                messageSegments.Add(nucleotide);
            }

            IList<string> sequences = new List<string>();
            IList<string> complements = new List<string>();
            
            string leftPart;
            string rightPart;

            while (!areEqual)
            {
                if (attempts > 20)
                {
                    throw new ModelException("Error en Encripcion por Par Complementario. Se superaron los 20 intentos.");
                }

                sb.Clear();
                attempts++;
                
                sequences.Clear();
                complements.Clear();

                for (int i = 0; i < p; i++)
                {
                    leftPart = DNASequence.GetRandomSequence(k + 1).ToString();
                    sequences.Add(string.Format("T{0}T", leftPart));

                    rightPart = ComplementRule.GetComplement(leftPart);
                    complements.Add(string.Format("T{0}T", rightPart));
                }
                
                bool useComplement = false;
                int messageIndex = 0;
                int pairIndex = 0;

                for (int i = 0; i < baseSequence.Length(); i++)
                {
                    if ((i != 1) && (i % 2 != 0) && (pairIndex < sequences.Count()))
                    {
                        if (!useComplement)
                        {
                            if (messageIndex < messageSegments.Count())
                            {
                                sb.Append(
                                    baseSequence.ToString()[i] + messageSegments[messageIndex].ToString()
                                    + sequences[pairIndex]);

                                messageIndex++;
                            }
                            else
                            {
                                sb.Append(baseSequence.ToString()[i] + sequences[pairIndex]);
                            }

                            useComplement = true;
                        }
                        else
                        {
                            sb.Append(baseSequence.ToString()[i] + complements[pairIndex]);
                            useComplement = false;
                            pairIndex++;
                        }
                    }
                    else
                    {
                        sb.Append(baseSequence.ToString()[i]);
                    }
                }

                IList<string> longestResult = GetLongestComplementarySubstringList(new DNASequence(sb.ToString()));

                _longestComplementsLength = k;

                areEqual = CompareSequenceList(sequences, longestResult);
            }


            return new DNASequence(sb.ToString());
        }
        

        public string Decrypt(IList<DNASequence> sequences, DNASequence baseSequence)
        {
            string retMessage = string.Empty;
            bool encontrado = false;

            foreach (DNASequence fakeSequence in sequences)
            {
                IList<string> complementedSequences = this.GetLongestComplementarySubstringList(fakeSequence);
                
                if ((this.LongestComplementsLength + 1) == complementedSequences[0].Length)
                {
                    //tengo una fakesequence que cumple mi requisito del largo....

                    int startPos = 0;
                    string sequenceToCompare = string.Empty;

                    foreach (string complementedBase in complementedSequences)
                    {
                        int pos = fakeSequence.ToString().IndexOf("T" + complementedBase + "T", startPos);

                        if (pos > -1)
                        {
                            //tomo el anterior al elemento complementado...
                            retMessage += fakeSequence.ToString()[pos - 1];

                            //borro este elemento complementado (mas 1 por cada T mas 1 por el puchito de mensaje)
                            int startDel = pos - 1;
                            //int endDel = complementedBase.Length + 3;

                            sequenceToCompare += fakeSequence.ToString().Substring(startPos, startDel - startPos);
                            //sequenceToCompare += fakeSequence.ToString().Remove(startDel, endDel);


                            //borro el complemento
                            int startDelComp = fakeSequence.ToString().IndexOf("T" + ComplementRule.GetComplement(complementedBase) + "T", 
                                startDel + complementedBase.Length + 3 -1);
                            //int endDelComp = complementedBase.Length + 2;

                            sequenceToCompare +=
                                fakeSequence.ToString().Substring(
                                    startDel + complementedBase.Length + 3,
                                    startDelComp - (startDel + complementedBase.Length + 3));

                            startPos = startDelComp + complementedBase.Length + 2;

                            encontrado = true;
                        }
                        else
                        {
                            //probar con otra fakesquence
                            encontrado = false;
                            break;
                        }
                    }

                    if (encontrado)
                    {
                        //copiar el resto de la fake sequence
                        sequenceToCompare += fakeSequence.ToString().Substring(startPos);

                        if (sequenceToCompare == baseSequence.ToString())
                        {
                            break;
                        }
                        else
                        {
                            throw new ModelException("Error en Desencripcion por Par Complementario. El largo del complemento es incorrecto.");
                        }
                    }
                    else
                    {
                        //pruebo otra fakesequence
                        continue;
                    }
                }
            }

            if (encontrado)
            {
                BinaryCodingRule binaryCodingRule = new BinaryCodingRule();
                DNASequence codedMessage = new DNASequence(retMessage);

                return EncryptionUtils.GetMessageFromBinary(codedMessage.ToBinary());
            }
            else
            {
                throw new ModelException("Error en Desencripcion por Par Complementario. No se encontro cadena base.");
            }
        }

        public bool NeedsRandomListOfSequences()
        {
            return true;
        }

        public int ExtraEncryptionOutput
        {
            get
            {
                return this.LongestComplementsLength;
            }
        }
    }
}
